/**
 * @author Chris Wilcox
 * @author Preston Parrett
 * 
 * @date 2008/03/17
 * 
 * The Evolution program was written for UCSB's CS165b class.
 * 
 * References: The Genome code structure is inspired by
 * David Eck's Genetic Algorithms example found at:
 * http://math.hws.edu/xJava/GA/index.html
 */

package evolution;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import evolution.Direction;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;


// Consumers are comparable because one which has a higher score is
// greater than one with a lower score
public class Consumer implements Life, Comparable<Consumer> {
	
	// Fitness
	private int score;
	
	// Properties of the Consumer
	public boolean alive = true;
	//private final int metabolism = 100;
	//private int size;
	//private int speed;
	private int energy;
	//private int MOVEMENT_COST = 30; 
	
	// Positioning
	private World home;
	private int row;
	private int col;
	public Direction facing;
	private final int SPACE=0, WALL=1, PRODUCER=2, CONSUMER=3;
	private final int INST_FORWARD=0, INST_BACK=1, INST_RIGHT=2, INST_LEFT=3;		
	
	// His Genes
	private Genome genes;
	private int generation;
	private int BEHAVIOR_CAP = 16;
	private int currentBehavior = 0;
	private int INSTINCTS = 4;
	private double MUTATE_FRACTION = 0.1;
	private double CROSSOVER_FRACTION = 0.5;

	
	public Consumer(World home, int row, int col) {
		this.home = home;
		resetLocation(row,col);
		
		genes = new Genome();
		generation = 0;
		
		score = 0;
		
		turnRandomly();
		
		energy = 1000;
		
		Stats.report("Number " + name(), 1);
	}
	
	/**
	 * Sets the location of this consumer
	 * @param row
	 * @param col
	 */
	public void resetLocation(int row, int col) {
		this.row = row;
		this.col = col;
	}
	
	/**
	 * Eat the other Life.
	 * 
	 * The other life form is removed from the World and its remaining
	 * energy is added to this life.
	 */
	public void eat(Life theMeal) {
		energy += theMeal.eaten();
		score++;
	}
	
	/**
	 * Called by the eat() method when another creature
	 * eats this one.
	 */
	public int eaten() {
		die();
		return energy;
	}
	
	
	public void die() {
		Stats.report("Number " + name(), -1);
		home.remove(row,col);
		
	}
	
	
	public String name() {
		return "Consumer";
	}
	
	
	
	/**
	 * Where this life thinks it is located.
	 */
	public int[] location() {
		return new int[] {row, col};
	}
	
	/*
	 * Any consumer will doSomething based on two pieces of information:
	 *  (1) what it can see in front of it at that time
	 *  (2) its current behavior state
	 *  
	 *  This should be where all the magic happens with the AI.
	 *  By the time this method is done, it should call the move(),
	 *  turn().
	 *  
	 *  Remember that eating another Life can be accomplished simply
	 *  by moving onto it.
	 */
	public void doSomethingInWorld() {

		boolean RANDOM = false;

		// Lifeforms move forward and turn randomly		
		if(RANDOM){	
			if ( Math.random() < 0.25 ) {
				turnRandomly();
			}
			else if ( Math.random() < .6 ) {
				move();
			}		
		}
		
		// Use the Consumer's Genome to decide what to do		
		else{	
			
			int visionInstinct =0;
			Visible whatISee = whatISee();
	
			// Find out what the fish sees in front of him
			if (whatISee == null){
				visionInstinct = SPACE;
			}
			else if(whatISee instanceof Wall ) {
				visionInstinct = WALL;
			}
			else if (whatISee instanceof Producer) {
				visionInstinct = PRODUCER;
			}		
			else if (whatISee instanceof Consumer) {
				visionInstinct = CONSUMER;
			}	
			
			// Choose newBehavior based on current behavior and what the fish sees
			int newBehavior = genes.nextBehavior[currentBehavior][visionInstinct];
			currentBehavior = newBehavior;
			
			// Perform the instinct action based on behavior and vision 
			int toDo = genes.instinct[currentBehavior][visionInstinct];
			
			switch(toDo){
				case INST_FORWARD:
					move();
					break;
				case INST_BACK:
					turn(Direction.RIGHT); // Turning twice reverses direction
					turn(Direction.RIGHT);
					break;
				case INST_RIGHT:
					turn(Direction.RIGHT);
					break;
				case INST_LEFT:
					turn(Direction.LEFT);
					break;		
			}				
		}
	}
	
	
	public Visible whatISee() {		
		int[] inFrontOfMe = inFrontOfMe();		
		Visible whatISee = home.at(inFrontOfMe[0],inFrontOfMe[1]);
		return whatISee;
	}
	
	
	public World whereILive() {
		return home;
	}
	
	
	// Returns location in front of fish
	public int[] inFrontOfMe() {
		int[] loc = new int[2];
		switch (facing) {
		case UP: 
			loc[0] = row-1;
			loc[1] = col; 
			break;
		case RIGHT:
			loc[0] = row;
			loc[1] = col+1; 
			break;
		case DOWN:
			loc[0] = row+1;
			loc[1] = col; 
			break;
		case LEFT:
			loc[0] = row;
			loc[1] = col-1;  
			break;
		default:
			System.err.println("FUCK YOU");
		}
		return loc;
	}
	
	// Turn and face a random direction
	private void turnRandomly() {
		switch ( (int)(Math.random()*4) ) {
		case 0:
			face(Direction.UP); break;
		case 1:
			face(Direction.RIGHT); break;
		case 2:
			face(Direction.DOWN); break;
		case 3:
			face(Direction.LEFT); break;
		}
	}
	
	// Turn based on facing direction
	public void turn(Direction toTurn){
		switch (toTurn){
			case UP:
				face(Direction.UP);
				break;
			case DOWN:
				face(Direction.DOWN);
				break;
			case RIGHT:
				switch (facing){
					case UP: face(Direction.RIGHT); break;
					case RIGHT: face(Direction.DOWN); break;
					case DOWN: face(Direction.LEFT); break;
					case LEFT: face(Direction.UP); break;
				}			
				break;
			case LEFT:
				switch (facing){
					case UP: face(Direction.LEFT); break;
					case RIGHT: face(Direction.UP); break;
					case DOWN: face(Direction.RIGHT); break;
					case LEFT: face(Direction.DOWN); break;
				}
			break;
		}
	}
	
	// Face direction
	public void face(Direction toFace) {
		facing = toFace;
	}
	
	/**
	 * Move forward one space in the direction you are facing
	 * 
	 * This should be a 'safe' move. As in you can call it in
	 * any situation and it will happen if it can.
	 */
	public void move() {
		
		int oldrow=row, oldcol=col;
		
		// If the location where this guy thinks he is located
		// and where the world thinks he is located, don't match
		// something went terrible wrong!
		assert (home.at(row,col) == this );
		
		Visible whatISee = whatISee();
		if ( whatISee instanceof Wall ) {
			// Don't do anything
			return;
		}
		else {
			//energy -= MOVEMENT_COST;
			int[] inFront = inFrontOfMe();
			
			if ( whatISee == null) {
				home.remove(row,col);
				row = inFront[0];
				col = inFront[1];
				home.place(this, inFront[0], inFront[1]);
			}
			else if (whatISee instanceof Producer) {
				// For now, lets not do any eating! Just do nothing
				home.remove(row,col);
				eat((Life)whatISee);
				row = inFront[0];
				col = inFront[1];
				home.place(this, inFront[0], inFront[1]);
			} else { 
				return;
			}
				
		}
		if (energy < 0) {
			die();
		}
		
		assert Math.abs(row-oldrow) <= 1;
		assert Math.abs(col-oldcol) <= 1;
		
		
	}
	
	
	/////////////////////
	// REPRODUCTION
	/////////////////////
	
	public Consumer[] mate(Consumer mate) {
		
		Consumer temp1 = new Consumer(home,0,0);
		Consumer temp2 = new Consumer(home,0,0);
		
		temp1.generation = Math.max(this.generation,mate.generation)+1;
		temp2.generation = temp1.generation;
		
		// Create new children with crossed genes from parents
		genes.copyGenomeTo(temp1.genes);
		((Consumer)mate).genes.copyGenomeTo(temp2.genes);	
		temp1.genes.crossGenome(temp2.genes);
				
		// Probablisticly mutate first child
		if (Math.random() > MUTATE_FRACTION) {
			temp1.genes.mutate();
		}
		
		// Probablisticly mutate second child
		if (Math.random() > MUTATE_FRACTION) {
			temp2.genes.mutate();
		}
						
		return new Consumer[] { temp1, temp2 };

	}
	
	public int fitness() {
		return score;
	}
	
	public void resetFitness() {
		score = 0;
	}
	
	public int compareTo(Consumer o) {
		if (this.score < o.fitness())       return 1;
		else if (this.score == o.fitness()) return 0;
		else 							return -1;
	}
	
	
	/**
	 * The Genome holds the heriditary information passed down from
	 * generation to generation of Consumer which specifies their behavior.
	 */
	private class Genome {
		//private int size;
		//private int speed;
		
		// Create tables of reactions to instinct and instinct capacity 
		int[][] nextBehavior = new int[BEHAVIOR_CAP][INSTINCTS];
		int[][] instinct = new int[BEHAVIOR_CAP][INSTINCTS]; 
		
		public Genome() {
			// Create new randomized genome
			for(int i=0; i<BEHAVIOR_CAP; i++){
				for(int j=0; j<INSTINCTS; j++){
					nextBehavior[i][j] = (int)(Math.random()*BEHAVIOR_CAP);
					instinct[i][j] = (int)(Math.random()*INSTINCTS);
				}
			}		
			
		}

		/*
		 * Copies genome copy from Consumer genome to g
		 */
		void copyGenomeTo(Genome g){
	        
			// Perform genome copy
			for(int i=0; i<BEHAVIOR_CAP; i++){
				for(int j=0; j<INSTINCTS; j++){
					g.nextBehavior[i][j] = nextBehavior[i][j];
					g.instinct[i][j] = instinct[i][j];					
				}
			}	
		}
		
		
		/*
		 * Cross two Genomes by choosing a random crossover location
		 */				
		void crossGenome(Genome g) {
			
	         int counter = 0;
	         int tempGenome = 0;  
         
	         // Choose random location for crossover
	         int crossOperator = (int)(Math.random()*128);	         
	         
	         // Perform crossover on new instinct and instinct
			for(int i=0; i<BEHAVIOR_CAP; i++){
				for(int j=0; j<INSTINCTS; j++){
		              
					if (counter > crossOperator) {						
						// new instinct cross
						tempGenome  = nextBehavior[i][j];
						nextBehavior[i][j] = g.nextBehavior[i][j];
						g.nextBehavior[i][j] = tempGenome;						
						// instinct cross
						tempGenome  = instinct[i][j];
						instinct[i][j] = g.instinct[i][j];
	                    g.instinct[i][j] = tempGenome;
	               }
			       counter++;					
				}
			}
		}
		
		

						
		/*
		 * Mutate this Genome by a simple change in a single
		 * part of the genetic code.
		 * 
		 * This happens with probability MUTATE_PERCENT. Rates of mutation should
		 * occur in the calling mating function.
		 */
		public void mutate() {
			
	         // Choose random location for mutation         				 
	         int i = (int)(Math.random()*BEHAVIOR_CAP);
	         int j = (int)(Math.random()*INSTINCTS);
	         
	         // Mutate genome to random value
			nextBehavior[i][j] = (int)(Math.random()*BEHAVIOR_CAP);
			instinct[i][j] = (int)(Math.random()*INSTINCTS);	         	        	         

		}
				
		
		
	}
	
	
	/////////////////////////
	// GUI
	/////////////////////////
	public void draw(Graphics OSG, int row, int col, int cellSize) {
		OSG.setColor(Color.BLACK);
	      int left, right, top, bottom;
	      int h, v;
	      left = (col - 1) * cellSize + 1;
	      top =  (row - 1) * cellSize + 1;
	      right = left + cellSize - 2;
	      bottom = top + cellSize - 2;
	      h = (left + right) / 2;
	      v = (top + bottom) / 2;
	      Font oldFont = OSG.getFont();
	      OSG.setFont(new Font(oldFont.getName(),Font.PLAIN,9));
		  OSG.drawString(String.valueOf(fitness()),left,top);
	      
		  /*BufferedImage fish = null;
		  try{
			  fish = ImageIO.read(new File("clownfish_small.jpg"));
		  } 
		  catch (IOException e){			  
		  }*/
		  
		  //int w = img.getWidth(null);
		  //int h = img.getHeight(null);
		  //BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
		  //Graphics g = bi.getGraphics();

		  
		  switch (facing) {
	         case RIGHT:  {
	        	 
	   		  	   //OSG.drawImage(fish, left, top, null);
	               OSG.drawLine(left,top,left,bottom-1);
	               OSG.drawLine(left,top,right-1,v);
	               OSG.drawLine(left,bottom,right-1,v);
	               break;
	            }
	         case UP:  {
	               OSG.drawLine(left,bottom-1,right-1,bottom-1);
	               OSG.drawLine(h,top,left,bottom-1);
	               OSG.drawLine(h,top,right,bottom-1);
	               break;
	            }
	         case LEFT:  {
	               OSG.drawLine(right-1,top,right-1,bottom-1);
	               OSG.drawLine(right,top,left-1,v);
	               OSG.drawLine(right,bottom,left-1,v);
	               break;
	            }
	         case DOWN:  {
	               OSG.drawLine(left,top,right-1,top);
	               OSG.drawLine(h,bottom,left-1,top);
	               OSG.drawLine(h,bottom,right-1,top);
	               break;
	            }
	      }
	   }

	
	
}
